package org.tamal.classinfo;

import java.io.DataInputStream;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

/**
 * The class represents a single instruction. A Java virtual machine instruction
 * consists of an opcode specifying the operation to be performed, followed by
 * zero or more operands embodying values to be operated upon.
 * @author Tamal Kanti Nath
 */
public final class Instruction {

    /** Do nothing. */
    public static final int NOP = 0;
    /** Push the null object reference onto the operand stack. */
    public static final int ACONST_NULL = 1;
    /** Push the int constant -1 onto the operand stack. */
    public static final int ICONST_M1 = 2;
    /** Push the int constant 0 onto the operand stack. */
    public static final int ICONST_0 = 3;
    /** Push the int constant 1 onto the operand stack. */
    public static final int ICONST_1 = 4;
    /** Push the int constant 2 onto the operand stack. */
    public static final int ICONST_2 = 5;
    /** Push the int constant 3 onto the operand stack. */
    public static final int ICONST_3 = 6;
    /** Push the int constant 4 onto the operand stack. */
    public static final int ICONST_4 = 7;
    /** Push the int constant 5 onto the operand stack. */
    public static final int ICONST_5 = 8;
    /** Push the long constant 0 onto the operand stack. */
    public static final int LCONST_0 = 9;
    /** Push the long constant 1 onto the operand stack. */
    public static final int LCONST_1 = 10;
    /** Push the float constant 0 onto the operand stack. */
    public static final int FCONST_0 = 11;
    /** Push the float constant 1 onto the operand stack. */
    public static final int FCONST_1 = 12;
    /** Push the float constant 2 onto the operand stack. */
    public static final int FCONST_2 = 13;
    /** Push the double constant 0 onto the operand stack. */
    public static final int DCONST_0 = 14;
    /** Push the double constant 1 onto the operand stack. */
    public static final int DCONST_1 = 15;
    /** Push the immediate (next) signed byte onto the operand stack. */
    public static final int BIPUSH = 16;
    /** Push the immediate (next) signed short onto the operand stack. */
    public static final int SIPUSH = 17;
    /** Push item from runtime constant pool. */
    public static final int LDC = 18;
    /** Push item from runtime constant pool. (wide index) */
    public static final int LDC_W = 19;
    /** Push long or double from runtime constant pool. (wide index) */
    public static final int LDC2_W = 20;
    /** Load int from local variable. */
    public static final int ILOAD = 21;
    /** Load long from local variable. */
    public static final int LLOAD = 22;
    /** Load float from local variable. */
    public static final int FLOAD = 23;
    /** Load double from local variable. */
    public static final int DLOAD = 24;
    /** Load reference from local variable. */
    public static final int ALOAD = 25;
    /** Load int at index 0 from local variable. */
    public static final int ILOAD_0 = 26;
    /** Load int at index 1 from local variable. */
    public static final int ILOAD_1 = 27;
    /** Load int at index 2 from local variable. */
    public static final int ILOAD_2 = 28;
    /** Load int at index 3 from local variable. */
    public static final int ILOAD_3 = 29;
    /** Load long at index 0 from local variable. */
    public static final int LLOAD_0 = 30;
    /** Load long at index 1 from local variable. */
    public static final int LLOAD_1 = 31;
    /** Load long at index 2 from local variable. */
    public static final int LLOAD_2 = 32;
    /** Load long at index 3 from local variable. */
    public static final int LLOAD_3 = 33;
    /** Load float at index 0 from local variable. */
    public static final int FLOAD_0 = 34;
    /** Load float at index 1 from local variable. */
    public static final int FLOAD_1 = 35;
    /** Load float at index 2 from local variable. */
    public static final int FLOAD_2 = 36;
    /** Load float at index 3 from local variable. */
    public static final int FLOAD_3 = 37;
    /** Load double at index 0 from local variable. */
    public static final int DLOAD_0 = 38;
    /** Load double at index 1 from local variable. */
    public static final int DLOAD_1 = 39;
    /** Load double at index 2 from local variable. */
    public static final int DLOAD_2 = 40;
    /** Load double at index 3 from local variable. */
    public static final int DLOAD_3 = 41;
    /** Load reference at index 0 from local variable. */
    public static final int ALOAD_0 = 42;
    /** Load reference at index 1 from local variable. */
    public static final int ALOAD_1 = 43;
    /** Load reference at index 2 from local variable. */
    public static final int ALOAD_2 = 44;
    /** Load reference at index 3 from local variable. */
    public static final int ALOAD_3 = 45;
    /** Load int from array. */
    public static final int IALOAD = 46;
    /** Load long from array. */
    public static final int LALOAD = 47;
    /** Load float from array. */
    public static final int FALOAD = 48;
    /** Load double from array. */
    public static final int DALOAD = 49;
    /** Load reference from array. */
    public static final int AALOAD = 50;
    /** Load byte or boolean from array. */
    public static final int BALOAD = 51;
    /** Load char from array. */
    public static final int CALOAD = 52;
    /** Load short from array. */
    public static final int SALOAD = 53;
    /** Store int into local variable. */
    public static final int ISTORE = 54;
    /** Store long into local variable. */
    public static final int LSTORE = 55;
    /** Store float into local variable. */
    public static final int FSTORE = 56;
    /** Store double into local variable. */
    public static final int DSTORE = 57;
    /** Store reference into local variable. */
    public static final int ASTORE = 58;
    /** Store int at index 0 into local variable. */
    public static final int ISTORE_0 = 59;
    /** Store int at index 1 into local variable. */
    public static final int ISTORE_1 = 60;
    /** Store int at index 2 into local variable. */
    public static final int ISTORE_2 = 61;
    /** Store int at index 3 into local variable. */
    public static final int ISTORE_3 = 62;
    /** Store long at index 0 into local variable. */
    public static final int LSTORE_0 = 63;
    /** Store long at index 1 into local variable. */
    public static final int LSTORE_1 = 64;
    /** Store long at index 2 into local variable. */
    public static final int LSTORE_2 = 65;
    /** Store long at index 3 into local variable. */
    public static final int LSTORE_3 = 66;
    /** Store float at index 0 into local variable. */
    public static final int FSTORE_0 = 67;
    /** Store float at index 1 into local variable. */
    public static final int FSTORE_1 = 68;
    /** Store float at index 2 into local variable. */
    public static final int FSTORE_2 = 69;
    /** Store float at index 3 into local variable. */
    public static final int FSTORE_3 = 70;
    /** Store double at index 0 into local variable. */
    public static final int DSTORE_0 = 71;
    /** Store double at index 1 into local variable. */
    public static final int DSTORE_1 = 72;
    /** Store double at index 2 into local variable. */
    public static final int DSTORE_2 = 73;
    /** Store double at index 3 into local variable. */
    public static final int DSTORE_3 = 74;
    /** Store reference at index 0 into local variable. */
    public static final int ASTORE_0 = 75;
    /** Store reference at index 1 into local variable. */
    public static final int ASTORE_1 = 76;
    /** Store reference at index 2 into local variable. */
    public static final int ASTORE_2 = 77;
    /** Store reference at index 3 into local variable. */
    public static final int ASTORE_3 = 78;
    /** Store into int array. */
    public static final int IASTORE = 79;
    /** Store into long array. */
    public static final int LASTORE = 80;
    /** Store into float array. */
    public static final int FASTORE = 81;
    /** Store into double array. */
    public static final int DASTORE = 82;
    /** Store into reference array. */
    public static final int AASTORE = 83;
    /** Store into byte or boolean array. */
    public static final int BASTORE = 84;
    /** Store into char array. */
    public static final int CASTORE = 85;
    /** Store into short array. */
    public static final int SASTORE = 86;
    /** Pop the top operand stack value. */
    public static final int POP = 87;
    /** Pop the top one or two operand stack values. */
    public static final int POP2 = 88;
    /** Duplicate the top operand stack value. */
    public static final int DUP = 89;
    /** Duplicate the top operand stack value and insert two values down. */
    public static final int DUP_X1 = 90;
    /**
     * Duplicate the top operand stack value and insert two or three values
     * down.
     */
    public static final int DUP_X2 = 91;
    /** Duplicate the top one or two operand stack values. */
    public static final int DUP2 = 92;
    /**
     * Duplicate the top one or two operand stack values and insert two or three
     * values down.
     */
    public static final int DUP2_X1 = 93;
    /**
     * Duplicate the top one or two operand stack values and insert two, three,
     * or four values down.
     */
    public static final int DUP2_X2 = 94;
    /** Swap the top two operand stack values. */
    public static final int SWAP = 95;
    /** Add int. */
    public static final int IADD = 96;
    /** Add long. */
    public static final int LADD = 97;
    /** Add float. */
    public static final int FADD = 98;
    /** Add double. */
    public static final int DADD = 99;
    /** Subtract int. */
    public static final int ISUB = 100;
    /** Subtract long. */
    public static final int LSUB = 101;
    /** Subtract float. */
    public static final int FSUB = 102;
    /** Subtract double. */
    public static final int DSUB = 103;
    /** Multiply int. */
    public static final int IMUL = 104;
    /** Multiply long. */
    public static final int LMUL = 105;
    /** Multiply float. */
    public static final int FMUL = 106;
    /** Multiply double. */
    public static final int DMUL = 107;
    /** Divide int. */
    public static final int IDIV = 108;
    /** Divide long. */
    public static final int LDIV = 109;
    /** Divide float. */
    public static final int FDIV = 110;
    /** Divide double. */
    public static final int DDIV = 111;
    /** Remainder int. */
    public static final int IREM = 112;
    /** Remainder long. */
    public static final int LREM = 113;
    /** Remainder float. */
    public static final int FREM = 114;
    /** Remainder double. */
    public static final int DREM = 115;
    /** Negate int. */
    public static final int INEG = 116;
    /** Negate long. */
    public static final int LNEG = 117;
    /** Negate float. */
    public static final int FNEG = 118;
    /** Negate double. */
    public static final int DNEG = 119;
    /** Shift left int. */
    public static final int ISHL = 120;
    /** Shift left long. */
    public static final int LSHL = 121;
    /** Arithmetic shift right int. */
    public static final int ISHR = 122;
    /** Arithmetic shift right long. */
    public static final int LSHR = 123;
    /** Logical shift right int. */
    public static final int IUSHR = 124;
    /** Logical shift right long. */
    public static final int LUSHR = 125;
    /** Boolean AND int. */
    public static final int IAND = 126;
    /** Boolean AND long. */
    public static final int LAND = 127;
    /** Boolean OR int. */
    public static final int IOR = 128;
    /** Boolean OR long. */
    public static final int LOR = 129;
    /** Boolean XOR int. */
    public static final int IXOR = 130;
    /** Boolean XOR Long. */
    public static final int LXOR = 131;
    /** Increment local variable by constant. */
    public static final int IINC = 132;
    /** Convert int to long. */
    public static final int I2L = 133;
    /** Convert int to float. */
    public static final int I2F = 134;
    /** Convert int to double. */
    public static final int I2D = 135;
    /** Convert long to int. */
    public static final int L2I = 136;
    /** Convert long to float. */
    public static final int L2F = 137;
    /** Convert long to double. */
    public static final int L2D = 138;
    /** Convert float to int. */
    public static final int F2I = 139;
    /** Convert float to long. */
    public static final int F2L = 140;
    /** Convert float to double. */
    public static final int F2D = 141;
    /** Convert double to int. */
    public static final int D2I = 142;
    /** Convert double to long. */
    public static final int D2L = 143;
    /** Convert double to float. */
    public static final int D2F = 144;
    /** Convert int to byte. */
    public static final int I2B = 145;
    /** Convert int to char. */
    public static final int I2C = 146;
    /** Convert int to short. */
    public static final int I2S = 147;
    /** Compare long. */
    public static final int LCMP = 148;
    /**
     * Compare float. Push -1 if one operand is NaN.<br/>
     * Used with {@link #IFLE} for greater than (&gt;) comparison.
     */
    public static final int FCMPL = 149;
    /**
     * Compare float. Push 1 if one operand is NaN.<br/>
     * Used with {@link #IFGE} for lesser than (&lt;) comparison.
     */
    public static final int FCMPG = 150;
    /**
     * Compare double. Push -1 if one operand is NaN.<br/>
     * Used with {@link #IFLE} for greater than (&gt;) comparison.
     */
    public static final int DCMPL = 151;
    /**
     * Compare double. Push 1 if one operand is NaN.<br/>
     * Used with {@link #IFGE} for lesser than (&lt;) comparison.
     */
    public static final int DCMPG = 152;
    /** Branch if popped int is zero. */
    public static final int IFEQ = 153;
    /** Branch if popped int is not zero. */
    public static final int IFNE = 154;
    /** Branch if popped int is less than zero. */
    public static final int IFLT = 155;
    /** Branch if popped int is greater than or equals to zero. */
    public static final int IFGE = 156;
    /** Branch if popped int is greater than zero. */
    public static final int IFGT = 157;
    /** Branch if popped int is less than or equals to zero. */
    public static final int IFLE = 158;
    /** Branch if both popped integers are equal. */
    public static final int IF_ICMPEQ = 159;
    /** Branch if both popped integers are not equal. */
    public static final int IF_ICMPNE = 160;
    /** Branch if second popped int &lt; first popped int. */
    public static final int IF_ICMPLT = 161;
    /** Branch if second popped int &gt;= first popped int. */
    public static final int IF_ICMPGE = 162;
    /** Branch if second popped int &gt; first popped int. */
    public static final int IF_ICMPGT = 163;
    /** Branch if second popped int &lt; first popped int. */
    public static final int IF_ICMPLE = 164;
    /** Branch if both popped references are equal. */
    public static final int IF_ACMPEQ = 165;
    /** Branch if both popped references are not equal. */
    public static final int IF_ACMPNE = 166;
    /** Branch always. */
    public static final int GOTO = 167;
    /** Jump subroutine. */
    public static final int JSR = 168;
    /** Return from subroutine. */
    public static final int RET = 169;
    /** Access jump table by index and jump. */
    public static final int TABLESWITCH = 170;
    /** Access jump table by key match and jump. */
    public static final int LOOKUPSWITCH = 171;
    /** Return int from method. */
    public static final int IRETURN = 172;
    /** Return long from method. */
    public static final int LRETURN = 173;
    /** Return float from method. */
    public static final int FRETURN = 174;
    /** Return double from method. */
    public static final int DRETURN = 175;
    /** Return reference from method. */
    public static final int ARETURN = 176;
    /** Return void from method. */
    public static final int RETURN = 177;
    /** Get static field from class. */
    public static final int GETSTATIC = 178;
    /** Set static field in class. */
    public static final int PUTSTATIC = 179;
    /** Fetch field from object. */
    public static final int GETFIELD = 180;
    /** Set field in object. */
    public static final int PUTFIELD = 181;
    /** Invoke instance method; dispatch based on class. */
    public static final int INVOKEVIRTUAL = 182;
    /**
     * Invoke instance method; special handling for superclass, private, and
     * instance initialization method invocations.
     */
    public static final int INVOKESPECIAL = 183;
    /** Invoke a class (static) method. */
    public static final int INVOKESTATIC = 184;
    /** Invoke interface method. */
    public static final int INVOKEINTERFACE = 185;
    /** Invoke dynamic method. */
    public static final int INVOKEDYNAMIC = 186;
    /** Create new object. */
    public static final int NEW = 187;
    /** Create new array. */
    public static final int NEWARRAY = 188;
    /** Create new array of reference. */
    public static final int ANEWARRAY = 189;
    /** Get length of array. */
    public static final int ARRAYLENGTH = 190;
    /** Throw exception or error. */
    public static final int ATHROW = 191;
    /** Check whether object is of given type. */
    public static final int CHECKCAST = 192;
    /** Determine if object is of given type. */
    public static final int INSTANCEOF = 193;
    /** Enter monitor for object. */
    public static final int MONITORENTER = 194;
    /** Exit monitor for object. */
    public static final int MONITOREXIT = 195;
    /** Extend local variable index by additional bytes. */
    public static final int WIDE = 196;
    /** Create new multidimensional array. */
    public static final int MULTIANEWARRAY = 197;
    /** Branch if reference is null. */
    public static final int IFNULL = 198;
    /** Branch if reference not null. */
    public static final int IFNONNULL = 199;
    /** Branch always (wide index). */
    public static final int GOTO_W = 200;
    /** Jump subroutine (wide index). */
    public static final int JSR_W = 201;

    private final int opcode;
    private final int offset;
    private final int size;
    private Integer wideOpcode;
    private Integer index;
    private Integer value;
    private Integer def;
    private Integer low;
    private Integer high;
    private int[] matches;
    private int[] offsets;

    private Instruction(DataInputStream in, int offset) throws IOException {
        this.offset = offset;
        opcode = in.readUnsignedByte();
        if (202 <= opcode && opcode <= 255) {
            throw new ClassFormatException("Invalid Instruction: " + opcode
                + " at offset " + offset);
        }
        int pad;
        switch (opcode) {
        case ALOAD:
        case ASTORE:
        case DLOAD:
        case DSTORE:
        case FLOAD:
        case FSTORE:
        case ILOAD:
        case ISTORE:
        case LDC:
        case LLOAD:
        case LSTORE:
        case RET:
            index = in.readUnsignedByte();
            size = 2;
            break;
        case ANEWARRAY:
        case CHECKCAST:
        case GETFIELD:
        case GETSTATIC:
        case GOTO:
        case IF_ACMPEQ:
        case IF_ACMPNE:
        case IF_ICMPEQ:
        case IF_ICMPGE:
        case IF_ICMPGT:
        case IF_ICMPLE:
        case IF_ICMPLT:
        case IF_ICMPNE:
        case IFEQ:
        case IFGE:
        case IFGT:
        case IFLE:
        case IFLT:
        case IFNE:
        case IFNONNULL:
        case IFNULL:
        case INSTANCEOF:
        case INVOKESPECIAL:
        case INVOKESTATIC:
        case INVOKEVIRTUAL:
        case JSR:
        case LDC_W:
        case LDC2_W:
        case NEW:
        case PUTFIELD:
        case PUTSTATIC:
            index = in.readUnsignedShort();
            size = 3;
            break;
        case BIPUSH:
        case NEWARRAY:
            value = (int) in.readByte();
            size = 2;
            break;
        case GOTO_W:
        case JSR_W:
            index = in.readInt();
            size = 5;
            break;
        case IINC:
            index = in.readUnsignedByte();
            value = in.readUnsignedByte();
            size = 3;
            break;
        case INVOKEDYNAMIC:
            index = in.readUnsignedShort();
            in.read();
            in.read();
            size = 5;
            break;
        case INVOKEINTERFACE:
            index = in.readUnsignedShort();
            value = in.readUnsignedByte();
            in.read();
            size = 5;
            break;
        case LOOKUPSWITCH:
            pad = 0;
            for (int i = offset + 1; i % 4 != 0; ++i) {
                ++pad;
                in.read();
            }
            def = in.readInt();
            matches = new int[in.readInt()];
            offsets = new int[matches.length];
            for (int i = 0; i < matches.length; ++i) {
                matches[i] = in.readInt();
                offsets[i] = in.readInt();
            }
            size = 9 + pad + matches.length * 8;
            break;
        case MULTIANEWARRAY:
            index = in.readUnsignedShort();
            value = in.readUnsignedByte();
            size = 4;
            break;
        case SIPUSH:
            value = in.readUnsignedShort();
            size = 3;
            break;
        case TABLESWITCH:
            pad = 0;
            for (int i = offset + 1; i % 4 != 0; ++i) {
                ++pad;
                in.read();
            }
            def = in.readInt();
            low = in.readInt();
            high = in.readInt();
            offsets = new int[high - low + 1];
            for (int i = 0; i < offsets.length; ++i) {
                offsets[i] = in.readInt();
            }
            size = 13 + pad + offsets.length * 4;
            break;
        case WIDE:
            wideOpcode = in.readUnsignedByte();
            switch (wideOpcode) {
            case ALOAD:
            case ASTORE:
            case DLOAD:
            case DSTORE:
            case FLOAD:
            case FSTORE:
            case ILOAD:
            case ISTORE:
            case LLOAD:
            case LSTORE:
            case RET:
                index = in.readUnsignedShort();
                size = 4;
                break;
            case IINC:
                index = in.readUnsignedShort();
                value = in.readUnsignedShort();
                size = 6;
                break;
            default:
                throw new ClassFormatException("Invalid wide Instruction: "
                        + opcode + " at offset: " + offset);
            }
            break;
        default:
            size = 1;
            break;
        }
    }

    /**
     * Returns the opcode.
     * @return the opcode
     */
    public int getOpcode() {
        return opcode;
    }

    /**
     * Returns the mnemonic.
     * @return the mnemonic
     */
    public String getMnemonic() {
        return getMnemonicForOpcode(opcode);
    }

    /**
     * Returns the mnemonic for opcode.
     * @param opcode the opcode
     * @return the mnemonic
     */
    public static String getMnemonicForOpcode(int opcode) {
        for (java.lang.reflect.Field f: Instruction.class.getDeclaredFields()) {
            int m = f.getModifiers();
            try {
                if (Modifier.isStatic(m)
                    && Modifier.isFinal(m)
                    && int.class.equals(f.getType())
                    && f.getInt(null) == opcode) {
                    return f.getName().toLowerCase();
                }
            } catch (IllegalAccessException e) {
                throw new ClassFormatException(e);
            }
        }
        return null;
    }

    /**
     * Returns the offset (code index).
     * @return the offset
     */
    public int getOffset() {
        return offset;
    }

    /**
     * Returns the size of the instruction.
     * @return the size
     */
    public int getSize() {
        return size;
    }

    /**
     * Returns the opcode that is expanded by 'wide'.
     * @return the wideOpcode
     * @see #WIDE
     */
    public Integer getWideOpcode() {
        return wideOpcode;
    }

    /**
     * Returns the index.
     * @return the index
     */
    public Integer getIndex() {
        return index;
    }

    /**
     * Returns the value.
     * @return the value
     */
    public Integer getValue() {
        return value;
    }

    /**
     * Returns default offset for 'table switch' or 'lookup switch'.
     * @return the default offset
     * @see #TABLESWITCH
     * @see #LOOKUPSWITCH
     */
    public Integer getDefault() {
        return def;
    }

    /**
     * Returns lowest match for 'table switch'.
     * @return the low
     * @see #TABLESWITCH
     */
    public Integer getLow() {
        return low;
    }

    /**
     * Returns highest match for 'table switch'.
     * @return the high
     * @see #TABLESWITCH
     */
    public Integer getHigh() {
        return high;
    }

    /**
     * Returns the match array for 'lookup switch'.
     * @return the matches
     * @see #LOOKUPSWITCH
     */
    public int[] getMatches() {
        return matches;
    }

    /**
     * Returns offsets array for 'table switch' or 'lookup switch'.
     * @return the offsets
     * @see #TABLESWITCH
     * @see #LOOKUPSWITCH
     */
    public int[] getOffsets() {
        return offsets;
    }

    @Override
    public String toString() {
        return offset + ":\t" + getMnemonic() + " [" + size + "]";
    }

    /**
     * Reads variable length instructions from input stream.
     * @param in the DataInputStream
     * @param size the code size
     * @return array of instructions
     * @throws IOException exception
     * @see Code
     */
    static Instruction[] readInstructions(DataInputStream in, int size)
        throws IOException {
        List<Instruction> instruction = new ArrayList<>();
        int bytesRead = 0;
        do {
            Instruction i = new Instruction(in, bytesRead);
            bytesRead += i.size;
            instruction.add(i);
        } while (bytesRead < size);
        if (bytesRead != size) {
            throw new ClassFormatException("Invalid code size");
        }
        return instruction.toArray(new Instruction[0]);
    }
}
